package com.edu.dk.security.impl;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.edu.dk.security.*;
import com.edu.dk.security.exception.CookieRejectedException;
import com.edu.dk.security.exception.RedirectLocationRejectedException;
import com.edu.dk.security.exception.ResponseHeaderRejectedException;
import com.edu.dk.security.util.CookieSupport;
import com.edu.dk.security.util.StringEscapeUtils;
import com.edu.dk.security.util.StringUtils;

import java.io.IOException;
import java.net.URI;

/**
 * 包裹<code>HttpServletResponse</code>，使之具备：
 * <ul>
 * <li>Header的安全性：过滤CRLF。</li>
 * <li>Cookie的安全性：限制cookie的大小。</li>
 * </ul>
 *
 */
public class BasicResponseImpl extends AbstractResponseWrapper {
	private static final String LOCATION_HEADER = "Location";
	private static final String SET_COOKIE_HEADER = "Set-Cookie";
	private final Object[] interceptors;

	public BasicResponseImpl(WebContext context, HttpServletResponse response, Object[] interceptors) {
		super(context, response);
		if (interceptors == null) {
			this.interceptors = new Object[0];
		} else {
			this.interceptors = interceptors;
		}
	}

	// -------------------------------------header--------------------------------------
	@Override
	public void addDateHeader(String name, long date) {
		super.addDateHeader(checkHeaderName(name), date);
	}

	@Override
	public void setDateHeader(String name, long date) {
		super.setDateHeader(checkHeaderName(name), date);
	}

	@Override
	public void addIntHeader(String name, int value) {
		super.addIntHeader(checkHeaderName(name), value);
	}

	@Override
	public void setIntHeader(String name, int value) {
		super.setIntHeader(checkHeaderName(name), value);
	}

	@Override
	public void addHeader(String name, String value) {
		name = StringUtils.trimToNull(name);

		if (LOCATION_HEADER.equalsIgnoreCase(name)) {
			value = checkRedirectLocation(value, false);

			if (value != null) {
				super.setHeader(LOCATION_HEADER, value); // force SET header
			}
		} else if (SET_COOKIE_HEADER.equalsIgnoreCase(name)) {
			value = checkCookieHeaderValue(name, value, false);

			if (value != null) {
				super.addHeader(SET_COOKIE_HEADER, value);
			}
		} else {
			name = checkHeaderName(name);
			value = checkHeaderValue(name, value);

			if (value != null) {
				super.addHeader(name, value);
			}
		}
	}

	@Override
	public void setHeader(String name, String value) {
		name = StringUtils.trimToNull(name);
		if (LOCATION_HEADER.equalsIgnoreCase(name)) {
			value = checkRedirectLocation(value, false);
			if (value != null) {
				super.setHeader(LOCATION_HEADER, value);
			}
		} else if (SET_COOKIE_HEADER.equalsIgnoreCase(name)) {
			value = checkCookieHeaderValue(name, value, true);
			if (value != null) {
				super.setHeader(SET_COOKIE_HEADER, value);
			}
		} else {
			name = checkHeaderName(name);
			value = checkHeaderValue(name, value);
			if (value != null) {
				super.setHeader(name, value);
			}
		}
	}

	private String checkHeaderName(String name) throws ResponseHeaderRejectedException {
		String newName = name; // name==null报错
		if (newName == null) {
			throw new IllegalArgumentException("header name is null");
		}
		for (Object interceptor : interceptors) {
			if (interceptor instanceof HeaderNameInterceptor) {
				newName = ((HeaderNameInterceptor) interceptor).checkHeaderName(newName);
				if (newName == null) {
					break;
				}
			}
		}
		if (newName == null) {
			throw new ResponseHeaderRejectedException("HTTP header rejected: " + StringEscapeUtils.escapeJava(name));
		}
		return newName;
	}

	private String checkHeaderValue(String name, String value) throws ResponseHeaderRejectedException {
		if (value == null) {
			return null; // value==null返回
		}
		String newValue = value;
		for (Object interceptor : interceptors) {
			if (interceptor instanceof HeaderValueInterceptor) {
				newValue = ((HeaderValueInterceptor) interceptor).checkHeaderValue(name, newValue);

				if (newValue == null) {
					break;
				}
			}
		}
		if (newValue == null) {
			throw new ResponseHeaderRejectedException("HTTP header rejected: " + StringEscapeUtils.escapeJava(name)
					+ "=" + StringEscapeUtils.escapeJava(value));
		}
		return newValue;
	}

	// -------------------------------------------cookie---------------------------------------
	@Override
	public void addCookie(Cookie cookie) {
		Cookie newCookie = checkCookie(cookie);
		CookieSupport newCookieSupport;
		if (newCookie instanceof CookieSupport) {
			newCookieSupport = (CookieSupport) newCookie;
		} else {
			newCookieSupport = new CookieSupport(newCookie); // 将cookie强制转化成cookie
																// support
		}
		newCookieSupport.addCookie(this); // 通过set-cookie header来添加cookie，以便统一监管
	}

	private Cookie checkCookie(Cookie cookie) throws CookieRejectedException {
		Cookie newCookie = cookie;
		if (newCookie == null) {
			throw new IllegalArgumentException("cookie is null!");
		}
		for (Object interceptor : interceptors) {
			if (interceptor instanceof CookieInterceptor) {
				newCookie = ((CookieInterceptor) interceptor).checkCookie(newCookie);

				if (newCookie == null) {
					break;
				}
			}
		}
		if (newCookie == null) {
			throw new CookieRejectedException("Cookie rejected: " + StringEscapeUtils.escapeJava(cookie.getName()) + "="
					+ StringEscapeUtils.escapeJava(cookie.getValue()));
		}
		return newCookie;
	}

	private String checkCookieHeaderValue(String name, String value, boolean setHeader) throws CookieRejectedException {
		if (value == null) {
			return null; // value==null返回
		}
		String newValue = value;
		for (Object interceptor : interceptors) {
			if (interceptor instanceof CookieHeaderValueInterceptor) {
				newValue = ((CookieHeaderValueInterceptor) interceptor).checkCookieHeaderValue(name, newValue,
						setHeader);
				if (newValue == null) {
					break;
				}
			}
		}
		if (newValue == null) {
			throw new CookieRejectedException("Set-Cookie rejected: " + StringEscapeUtils.escapeJava(value));
		}
		return newValue;
	}

	// -------------------------------sendError----------------------------------
	@Override
	public void sendError(int sc, String msg) throws IOException {
		msg = checkStatusMessage(sc, msg);
		if (msg == null) {
			super.sendError(sc);
		} else {
			super.sendError(sc, msg);
		}
	}

	// -------------------------------setStatus----------------------------------
	@Override
	@Deprecated
	public void setStatus(int sc, String msg) {
		msg = checkStatusMessage(sc, msg);
		if (msg == null) {
			super.setStatus(sc);
		} else {
			super.setStatus(sc, msg);
		}
	}

	private String checkStatusMessage(int sc, String msg) {
		if (msg != null) {
			for (Object interceptor : interceptors) {
				if (interceptor instanceof StatusMessageInterceptor) {
					msg = ((StatusMessageInterceptor) interceptor).checkStatusMessage(sc, msg);
					if (msg == null) {
						break;
					}
				}
			}
		}
		return msg;
	}

	// -------------------------------sendRedirect----------------------------------
	@Override
	public void sendRedirect(String location) throws IOException {
		super.sendRedirect(checkRedirectLocation(location, true));
	}

	private String checkRedirectLocation(String location, boolean notNull) throws RedirectLocationRejectedException {
		String newLocation = StringUtils.trimToNull(location);
		if (newLocation == null && !notNull) {
			return null;
		}
		newLocation = normalizeLocation(newLocation, getWebContext().getRequest());
		for (Object interceptor : interceptors) {
			if (interceptor instanceof RedirectLocationInterceptor) {
				newLocation = ((RedirectLocationInterceptor) interceptor).checkRedirectLocation(newLocation);
				if (newLocation == null) {
					break;
				}
			}
		}
		if (newLocation == null) {
			throw new RedirectLocationRejectedException(
					"Redirect location rejected: " + StringEscapeUtils.escapeJava(location));
		}
		return newLocation;
	}

	static String normalizeLocation(String location, HttpServletRequest request) {
		location = StringUtils.trimToNull(location);
		if (location == null) {
			throw new IllegalArgumentException("Location is null!");
		}
		URI locationURI = URI.create(location);
		if (!locationURI.isAbsolute()) {
			URI baseuri = URI.create(request.getRequestURL().toString());
			locationURI = baseuri.resolve(locationURI);
		}
		return locationURI.normalize().toString();
	}

	// -------------------------------------------------------------------
	public void prepareResponse() {
		for (Object interceptor : interceptors) {
			if (interceptor instanceof WebContextLifecycleInterceptor) {
				((WebContextLifecycleInterceptor) interceptor).prepare();
			}
		}
	}

	public void commitHeaders() {
		for (int i = interceptors.length - 1; i >= 0; i--) {
			Object interceptor = interceptors[i];
			if (interceptor instanceof WebContextLifecycleInterceptor) {
				((WebContextLifecycleInterceptor) interceptor).commitHeaders();
			}
		}
	}

	public void commitResponse() {
		for (int i = interceptors.length - 1; i >= 0; i--) {
			Object interceptor = interceptors[i];
			if (interceptor instanceof WebContextLifecycleInterceptor) {
				((WebContextLifecycleInterceptor) interceptor).commit();
			}
		}
	}
}
